#include "dx8_surface.h"
#include <d3dx8.h>
#include <ddraw.h>		// We're using a DDRAW stucture later

#include "math/math_core.h"
#include "graphics/graphics.h"
#include "dx8graphics/dx8_graphics.h"

#include "ui/ui_manager.h"
#include "util/log.h"				// log
#include "util/common_macros.h"		// macros
#include "util/mmgr.h"		


// resouce manager
#include "resman/resman.h"

// VFS
#include "vfs/vfs.h"

#include <cassert>

res::ResourceFile*	Dx8Surface::CreateImage()
{
	// construct
	return new Dx8Surface();
}

Dx8Surface::Dx8Surface() :
	Surface(),
	m_Format(0)
{
	m_Texture.pTexture = 0;

}

Dx8Surface::~Dx8Surface()
{

}


void	Dx8Surface::RegisterFormats()
{
	static bool AlreadyInitialized = false;
	if (AlreadyInitialized) return;
	AlreadyInitialized = true;

	res::ResMan& RM = res::ResMan::Instance();

	// register this resource constuctor with the following formats.
	RM.AddResourceConstructor(CreateImage, "jpg");
	RM.AddResourceConstructor(CreateImage, "tga");
	RM.AddResourceConstructor(CreateImage, "bmp");
	RM.AddResourceConstructor(CreateImage, "dds");
	RM.AddResourceConstructor(CreateImage, "png");

	_LOG(MSG_INIT, "File formats registered for surface");
}


void Dx8Surface::GetTexMemory(unsigned int &TexMem, unsigned int &TexMemComp, unsigned int &TexMemFullC, unsigned int &TexMemPal)
{
	TexMem = TexMemComp = TexMemFullC = TexMemPal = 0;
	if (!m_Texture.pTexture) return;
	if (m_IsCubemap)
	{

	} else
	{
		D3DRESOURCETYPE	Type = m_Texture.pTexture->GetType();

		D3DSURFACE_DESC	Desc;
		m_Texture.pTexture->GetLevelDesc(0, &Desc);
		if (Desc.Format>=D3DFMT_DXT1 && Desc.Format<=D3DFMT_DXT5)
		{
			TexMemComp+=Desc.Size;
		} else
		//if (Desc.Format ==D3DFMT_A8R8G8B8 || Desc.Format ==D3DFMT_R8G8B8)
		{
			TexMemFullC+=Desc.Size;
		} 
		 TexMem = TexMemFullC + TexMemComp + TexMemPal;
	}

}

Dx8Surface*		Dx8Surface::FromTexture(LPDIRECT3DTEXTURE8 pTex, int width, int height)
{
	Dx8Surface* pSurf = new Dx8Surface();
	pSurf->m_Texture.pTexture = pTex;
	pSurf->m_Format = D3DFMT_A8R8G8B8;	// assume for now
	pSurf->m_Width = width;
	pSurf->m_Height = height;
	pSurf->m_IsCubemap = false;
	pSurf->m_Size = width*height*4;

	// other resource style stuff?
	return pSurf;
}


void	Dx8Surface::InitTexture()
{
	Dx8Graphics* pDX = GetDXGraphics();
	HRESULT hRes;
	m_IsCubemap = TestIsCubemap();

	UINT			MipLevels = 1;
	DWORD			MipFilter = D3DX_FILTER_NONE;

	if (pDX->IsMipmapEnabled() == true)
	{
		MipLevels = D3DX_DEFAULT;
		MipFilter = D3DX_FILTER_LINEAR;
	}

	// create from surface.
    if (m_IsCubemap)
    {
		hRes = D3DXCreateCubeTextureFromFileInMemory(                         
                        pDX->GetDevice(),  // device
						m_Data.DataArray,
						m_Data.Size,
                        &m_Texture.pCubemap) ;	 // Cubemap
    } else
	{
		D3DPOOL			Pool	= D3DPOOL_MANAGED; // D3DPOOL_DEFAULT; 
		D3DFORMAT		Format	= D3DFMT_UNKNOWN;

		D3DXIMAGE_INFO Info;
		hRes = D3DXCreateTextureFromFileInMemoryEx(
						pDX->GetDevice(),  // device
						m_Data.DataArray,
						m_Data.Size,
						D3DX_DEFAULT, D3DX_DEFAULT,		// use default size
						MipLevels,						// number of mipmap levels - according to graphics
						0,								// just a normal texture
						Format,							// whatever format the file says
						D3DPOOL_MANAGED,				// default pool
						D3DX_FILTER_POINT,				// no outside filters
						MipFilter,				        // chosen mip filter
						0,								// no color-key
						&Info,							// information
						NULL,							// No palette
						&m_Texture.pTexture) ;	 //		// pointer
		m_Width = Info.Width;
		m_Height = Info.Height;
		m_Format = Info.Format;

		D3DSURFACE_DESC	Desc;
		m_Texture.pTexture->GetLevelDesc(0, &Desc);
		m_Size = Desc.Size;

/////////////////////////////////////////////////////////////
// HACK HACK HACK HACK :
// Trying with reduced size textures
	// find the largest size
/*	int	Size = UTIL_HIGH(m_Width, m_Height);
	if (Size > 64)
	{
		UTIL_SAFE_RELEASE(m_Texture.pTexture);

		Size = 64;
				
		_LOG(MSG_WARNING, "Clamping texture size for texture " << m_Name);
		// recreate it as square
		hRes = D3DXCreateTextureFromFileInMemoryEx(
						pDX->GetDevice(),  // device
						m_Data.DataArray,
						m_Data.Size,
						Size, Size,		// use default size
						MipLevels,						// number of mipmap levels - according to graphics,						// mipmaps designated from Graphics?
						0,								// just a normal texture
						Format,							// whatever format the file says
						D3DPOOL_MANAGED,				// default pool
						D3DX_FILTER_POINT,              // default filtering
						MipFilter,				        // chosen mip filter
						0,								// no color-key
						&Info,							// information
						NULL,							// No palette
						&m_Texture.pTexture) ;	 //		// pointer
		}*/

		if (pDX->HasBug(Dx8Graphics::BUG_SQUARE_TEXTURES))
		{
			// destroy it
			UTIL_SAFE_RELEASE(m_Texture.pTexture);

			// find the largest size
			int	Size = UTIL_HIGH(m_Width, m_Height);
			// cap to the nearest Pow2?
			if (Size > 512) Size = 512; else
			if (Size > 256) Size = 256; else
			if (Size > 128) Size = 128; else
			if (Size > 64) Size = 64; else
				Size = 32;
			
			_LOG(MSG_WARNING, "Clamping texture size for texture " << m_Name);
			// recreate it as square
			hRes = D3DXCreateTextureFromFileInMemoryEx(
							pDX->GetDevice(),  // device
							m_Data.DataArray,
							m_Data.Size,
							Size, Size,		// use default size
							MipLevels,						// number of mipmap levels - according to graphics,						// mipmaps designated from Graphics?
							0,								// just a normal texture
							Format,							// whatever format the file says
							D3DPOOL_MANAGED,				// default pool
							D3DX_FILTER_POINT,              // default filtering
							MipFilter,				        // chosen mip filter
							0,								// no color-key
							&Info,							// information
							NULL,							// No palette
							&m_Texture.pTexture) ;	 //		// pointer
			
			m_Width = Size;
			m_Height = Size;
			m_Format = Info.Format;
		}
	}
	if (!MathFunc::IsPow2(m_Width) || !MathFunc::IsPow2(m_Height))
	{
		_LOG(MSG_ERROR, "Texture " << m_Data.FileName << " is " << m_Width << "x" << m_Height << ". Only pow2 textures will run correctly on some hardware.");
	}
    if (SUCCEEDED(hRes))
    {
		m_State = RES_STATUS::RS_READY;
		_LOG(MSG_LOAD, "Loaded texture " << m_Data.FileName);
		ui::UIManager::Instance().UpdateLoadCB();
		
    } else
    {
		m_State = RES_STATUS::RS_FAILED;
		_LOG(MSG_LOAD, Dx8Graphics::GetDXError(hRes));
		_LOG(MSG_LOAD, "Failed to loaded texture " << m_Data.FileName);
    }
	// remember to free resource at the end of the process
}

bool			Dx8Surface::TestIsCubemap()
{
	unsigned char*	DataStart = &m_Data.DataArray[0];
	DWORD*			Magic = (DWORD*)DataStart;
	DDSURFACEDESC2*	ddsd = (DDSURFACEDESC2*)(DataStart + sizeof(Magic));		
	
    if (*Magic != (' SDD'))	// reverse of 'DDS '
        return false;

    if (ddsd->ddsCaps.dwCaps2 & DDSCAPS2_CUBEMAP) 
	{
		return true;
	}
	return false;
}

/// remove all data from RAM, but keep track of origin so that it can be reaquired
void			Dx8Surface::ReleaseResource()
{
	if (m_IsCubemap)
	{
		UTIL_SAFE_RELEASE(m_Texture.pCubemap);
	} else
	if (m_Texture.pTexture)
	{
		unsigned int Test = m_Texture.pTexture->Release();
		if (Test != 0)
		{
			int j = 0;	
		}

		m_Texture.pTexture = NULL;
	}
	return Surface::ReleaseResource();
}

bool			Dx8Surface::SetTextureStage(unsigned int _TexStage)
{
	HRESULT	hRes = S_OK;
	if (m_State == RES_STATUS::RS_RELEASED)
	{
		InitializeResource();
	} 
	if (m_State == RES_STATUS::RS_READY)
	{
		if (m_Texture.pTexture)
		{
			if (m_IsCubemap)
			{
	            hRes = GetDXGraphics()->GetDevice()->SetTexture(_TexStage, m_Texture.pCubemap);
			} else
			{
				hRes = GetDXGraphics()->GetDevice()->SetTexture(_TexStage, m_Texture.pTexture);
			}
		} else
		{
			GetDXGraphics()->GetDevice()->SetTexture(_TexStage, NULL);
		}
	} else
	{
		GetDXGraphics()->GetDevice()->SetTexture(_TexStage, NULL);
	}
	return true;
}

float*			Dx8Surface::GetColors()
{
	if (m_State == RES_STATUS::RS_RELEASED)
	{
		// we've released it. See if we can re-load it now?
		InitializeResource();
	} 
	if (!m_Texture.pTexture) return NULL;

	IDirect3DSurface8*	pSurf = NULL;
	if (m_Format == D3DFMT_A8R8G8B8 || m_Format == D3DFMT_X8R8G8B8)
	{
		m_Texture.pTexture->GetSurfaceLevel(0, &pSurf);
	} else
	if (m_Format == D3DFMT_DXT1 || m_Format == D3DFMT_DXT5)
	{
		pSurf = DDStoSurface();
	}
	if (!pSurf) return NULL;


	// lock the surface
	D3DLOCKED_RECT	Rect;
	HRESULT hres = pSurf->LockRect(&Rect, NULL, D3DLOCK_READONLY);
	if (FAILED(hres)) 
	{
		int	RefC = pSurf->Release();
		return NULL;
	}

	// create array
	float*	ColorA = new float[m_Height*m_Width*4];

	int	index = 0;
	// fill array
	for (unsigned int y=0; y<m_Height; y++)
	{
		DWORD*	pSource = (DWORD*)((unsigned char*)(Rect.pBits) + (Rect.Pitch*y));
		for (unsigned int x=0; x<m_Width; x++)
		{
			DWORD	Color = (*pSource);
			unsigned char	R = (unsigned char)((Color) & 0xFF);
			unsigned char	G = (unsigned char)((Color>>8) & 0xFF);
			unsigned char	B = (unsigned char)((Color>>16) & 0xFF);
			unsigned char	A = (unsigned char)((Color>>24) & 0xFF);

			ColorA[index++] = float(R);
			ColorA[index++] = float(G);
			ColorA[index++] = float(B);
			ColorA[index++] = float(A);

			pSource++;
		}
	}
	pSurf->UnlockRect();
	int	RefC = pSurf->Release();
	return ColorA;
}

COLOR*		Dx8Surface::GetPixels()
{
	if (m_State == RES_STATUS::RS_RELEASED)
	{
		// we've released it. See if we can re-load it now?
		InitializeResource();
	} 
	if (!m_Texture.pTexture) return NULL;



	IDirect3DSurface8*	pSurf = NULL;
	if (m_Format == D3DFMT_A8R8G8B8 || m_Format == D3DFMT_X8R8G8B8)
	{
		m_Texture.pTexture->GetSurfaceLevel(0, &pSurf);
	} else
	if (m_Format == D3DFMT_DXT1 || m_Format == D3DFMT_DXT5)
	{
		pSurf = DDStoSurface();
	}
	if (!pSurf) return NULL;

	// lock the surface
	D3DLOCKED_RECT	Rect;
	HRESULT hres = pSurf->LockRect(&Rect, NULL, D3DLOCK_READONLY);
	if (FAILED(hres))
	{
		int	RefC = pSurf->Release();
		return NULL;
	}

	// create array
	COLOR*	ColorA = new COLOR[m_Height*m_Width];

	// fill array
	for (unsigned int y=0; y<m_Height; y++)
	{
		DWORD*	pSource = (DWORD*)((unsigned char*)(Rect.pBits) + (Rect.Pitch*y));
		for (unsigned int x=0; x<m_Width; x++)
		{
			ColorA[x + (y*m_Width)] = (*pSource);
			pSource++;
		}
	}
	pSurf->UnlockRect();
	int	RefC = pSurf->Release();
	return ColorA;
}
IDirect3DSurface8*	Dx8Surface::DDStoSurface()
{
	if (!m_Texture.pTexture) return NULL;

	IDirect3DSurface8*	pSurf = NULL;
	IDirect3DSurface8*	pDest = NULL;
	HRESULT hres;

	hres = m_Texture.pTexture->GetSurfaceLevel(0, &pSurf);
	if (FAILED(hres))
	{
		return NULL;	// unable to grab it
	}

	// create the surface to copy/unpack to
	Dx8Graphics* pDX = GetDXGraphics();
	hres = pDX->GetDevice()->CreateImageSurface(m_Width,m_Height, D3DFMT_A8R8G8B8, &pDest);
	if (FAILED(hres))
	{
		int	RefC = pSurf->Release();		// probably ran out of vram. Free up the source, and return
		return NULL;
	}

	// copy over
	hres = D3DXLoadSurfaceFromSurface(
		pDest,	// destination surface
		NULL,	// no palette
		NULL,	// dest rect; NULL to specify whole surface
		pSurf,	// source
		NULL,	// source palette
		NULL,	// source rect
		D3DX_FILTER_NONE,	// ignore filtering
		0);		// no color key
	if (FAILED(hres))
	{
		// unable to copy. Might still be in use.
		// free both sources and return
		int	RefC;
		RefC = pDest->Release();		
		RefC = pSurf->Release();	
		return NULL;
	}
	pSurf->Release();	// okay, we're done
	return pDest;		// remember to free this up!
}

AlphaMask*	Dx8Surface::GenerateMask()
{
	if (m_State == RES_STATUS::RS_RELEASED)
	{
		// we've released it. See if we can re-load it now?
		InitializeResource();
	} 
	if (!m_Texture.pTexture) return NULL;

	// if not 32 bit, assert here
	IDirect3DSurface8*	pSurf = NULL;
	if (m_Format == D3DFMT_A8R8G8B8)
	{
		m_Texture.pTexture->GetSurfaceLevel(0, &pSurf);
	} else
	if (m_Format == D3DFMT_DXT5)
	{
		pSurf = DDStoSurface();
	}

	if (!pSurf) return NULL;

	D3DLOCKED_RECT	Rect;
	HRESULT hres = pSurf->LockRect(&Rect, NULL, D3DLOCK_READONLY);
	if (FAILED(hres)) 
	{
		int	RefC = pSurf->Release();
		return NULL;
	}
	
	AlphaMask* pMask = new AlphaMask();
	pMask->Height = m_Height;
	pMask->Width = m_Width;
	pMask->AlphaVal = new unsigned char [m_Height*m_Width];

	for (unsigned int y=0; y<m_Height; y++)
	{
		DWORD*	pSource = (DWORD*)((unsigned char*)(Rect.pBits) + (Rect.Pitch*y));
		for (unsigned int x=0; x<m_Width; x++)
		{
			DWORD	Color = (*pSource);

			unsigned char	R = (unsigned char)((Color) & 0xFF);
			unsigned char	G = (unsigned char)((Color>>8) & 0xFF);
			unsigned char	B = (unsigned char)((Color>>16) & 0xFF);
			unsigned char	A = (unsigned char)((Color>>24) & 0xFF);

			pMask->AlphaVal[x + (y*m_Width)] = A;

			pSource++;
		}
	}

	hres = pSurf->UnlockRect();
	int	RefC = pSurf->Release();
	if (FAILED(hres)) 
	{
		delete pMask;
		return NULL;
	}
	return pMask;
}

void	Dx8Surface::CopyOntoSource(D3DLOCKED_RECT* pRect, int Width, int Height, int OffsetX, int OffsetY)
{
	D3DLOCKED_RECT	Rect;
	IDirect3DSurface8 *	pSurf;

	if (!(m_Format == D3DFMT_A8R8G8B8)) 
		return;
	// get surface
	m_Texture.pTexture->GetSurfaceLevel(0, &pSurf);
	if (!pSurf) return;

	// lock the data rect
	HRESULT hres = pSurf->LockRect(&Rect, NULL, D3DLOCK_READONLY);
	if (FAILED(hres))
	{
		pSurf->Release();
		return;
	}
	

	for (int y=0; y<(int)m_Height; y++)
	{
		int	ToY = OffsetY + y;
		if (ToY < 0 || ToY >= Height) continue;	// skip row.. it's out of bounds

		DWORD*	pSource = (DWORD*)((unsigned char*)(Rect.pBits) + (Rect.Pitch*y));
		DWORD*	pDest	= (DWORD*)((unsigned char*)(pRect->pBits) + (pRect->Pitch*ToY));
		for (int x=0; x<(int)m_Width; x++)
		{
			int	ToX = x + OffsetX;
			if (ToX < 0 || ToX >= Width) continue;

			DWORD	SourceColor = pSource[x];
			DWORD	DestColor = pDest[ToX];

			float			R = float((SourceColor) & 0xFF);
			float			G = float((SourceColor>>8) & 0xFF);
			float			B = float((SourceColor>>16) & 0xFF);
			float			A = float((SourceColor>>24) & 0xFF);

			float			ToR = float((DestColor) & 0xFF);
			float			ToG = float((DestColor>>8) & 0xFF);
			float			ToB = float((DestColor>>16) & 0xFF);
			float			ToA = float((DestColor>>24) & 0xFF);

			A = A / 256.0f;
			ToA = ToA / 256.0f;

			float			FinalR = (R * A) + (ToR * (1.0f-A));
			float			FinalG = (G * A) + (ToG * (1.0f-A));
			float			FinalB = (B * A) + (ToB * (1.0f-A));
			float			FinalA = UTIL_HIGH(A, ToA);		//? what's the result supposed to be there?

			// Clamp to prevent over saturation
			UTIL_CLAMP(FinalR, 0, 1);
			UTIL_CLAMP(FinalG, 0, 1);
			UTIL_CLAMP(FinalB, 0, 1);
			UTIL_CLAMP(FinalA, 0, 1);

			// convert back to a color
			DWORD			Final = ColorfRGBA(FinalR, FinalG, FinalB, FinalA);


			pDest[ToX] = Final;
		}
	}

	hres = pSurf->UnlockRect();
	pSurf->Release();
}

/*void		Dx8Surface::StretchBlitOnto(Surface* pSurf, const math::Vec2& Offset, const math::Vec2& Size)
{
	Dx8Surface*	pSource = (Dx8Surface*)pSurf;

	IDirect3DSurface9 *	pFrom;
	IDirect3DSurface9 *	pTo;

	pSource->m_Texture.pTexture->GetSurfaceLevel(0, &pFrom);
	this->m_Texture.pTexture->GetSurfaceLevel(0, &pTo);


	RECT	From;
	From.top	= From.left = 0;
	From.right	= pSource->m_Width;
	From.bottom = pSource->m_Height;

	RECT	To;
	To.left		= (Offset.x * m_Width);
	To.right	= (To.left + (Size.x*m_Width));
	To.top		= (Offset.y * m_Height);
	To.bottom   = (To.top  + (Size.y*m_Height));

//	GetDXGraphics()->GetDevice()->StretchRect(pFrom, &From, pTo, &To, D3DTEXF_POINT );
//	HRESULT hres = D3DXLoadSurfaceFromSurface(pTo, NULL, &To, pFrom, NULL, &From, D3DX_FILTER_TRIANGLE, 0);

	HRESULT hRes;

	D3DLOCKED_RECT	LockedTo;
	hRes = pTo->LockRect(&LockedTo, &To, 0);

	D3DLOCKED_RECT	LockedFrom;
	hRes = pFrom->LockRect(&LockedFrom, NULL, D3DLOCK_READONLY);
}
*/
